Revision control

Copy as Markdown

/* 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 XCTest
// The event extra properties.
// This would be generated by the glean_parser usually.
struct ClickExtras: EventExtras {
var objectId: String?
var other: String?
func toExtraRecord() -> [String: String] {
var record = [String: String]()
if let objectId = self.objectId {
record["object_id"] = objectId
}
if let other = self.other {
record["other"] = other
}
return record
}
}
// The event extra properties.
// This would be generated by the glean_parser usually.
struct TestExtras: EventExtras {
var testName: String?
func toExtraRecord() -> [String: String] {
var record = [String: String]()
if let testName = self.testName {
record["test_name"] = testName
}
return record
}
}
// The event extra properties.
// This would be generated by the glean_parser usually.
struct SomeExtras: EventExtras {
var someExtra: String?
func toExtraRecord() -> [String: String] {
var record = [String: String]()
if let someExtra = self.someExtra {
record["some_extra"] = someExtra
}
return record
}
}
class TestEventListener: GleanEventListener {
let listenerTag = "TestEventListener"
var lastSeenId: String = ""
var count: Int64 = 0
func onEventRecorded(_ id: String) {
self.lastSeenId = id
self.count += 1
}
}
class EventMetricTypeTests: XCTestCase {
var expectation: XCTestExpectation?
var lastPingJson: [String: Any]?
private func setupHttpResponseStub() {
stubServerReceive { pingType, json in
if pingType != "events" {
// Skip non-events pings here.
// This might include the initial "active" baseline ping.
return
}
XCTAssert(json != nil)
self.lastPingJson = json
// Fulfill test's expectation once we parsed the incoming data.
DispatchQueue.main.async {
// Let the response get processed before we mark the expectation fulfilled
self.expectation?.fulfill()
}
}
}
override func setUp() {
resetGleanDiscardingInitialPings(testCase: self, tag: "EventMetricTypeTests")
}
override func tearDown() {
lastPingJson = nil
expectation = nil
tearDownStubs()
}
func testEventSavesToStorage() {
// Note: We specify both `Keys` and `Extras` here to ease testing.
// In user code only _one_ will be specified and the other will be its `NoExtra` variant,
// thus only allowing either the old API or the new one.
let metric = EventMetricType<ClickExtras>(CommonMetricData(
category: "ui",
name: "click",
sendInPings: ["store1"],
lifetime: .ping,
disabled: false
), ["object_id", "other"]
)
XCTAssertNil(metric.testGetValue())
// Newer API
metric.record(ClickExtras(objectId: "buttonA", other: "foo"))
// Some extra keys can be left undefined.
metric.record(ClickExtras(objectId: "buttonA"))
/* SKIPPED: resetting system clock to return fixed time value */
// Old API, this is available only because we manually implemented the enum.
// Generated code will have only one of the APIs available.
metric.record(ClickExtras(objectId: "buttonB", other: "bar"))
let events = metric.testGetValue()!
XCTAssertEqual(3, events.count)
XCTAssertEqual("ui", events[0].category)
XCTAssertEqual("click", events[0].name)
XCTAssertEqual("buttonA", events[0].extra?["object_id"])
XCTAssertEqual("foo", events[0].extra?["other"])
XCTAssertEqual("ui", events[1].category)
XCTAssertEqual("click", events[1].name)
XCTAssertEqual("buttonA", events[1].extra?["object_id"])
XCTAssertEqual(nil, events[1].extra?["other"])
XCTAssertEqual("ui", events[2].category)
XCTAssertEqual("click", events[2].name)
XCTAssertEqual("buttonB", events[2].extra?["object_id"])
XCTAssertEqual("bar", events[2].extra?["other"])
XCTAssertLessThanOrEqual(events[0].timestamp, events[1].timestamp, "The sequence of events must be preserved")
}
func testEventRecordedWithEmptyCategory() {
let metric = EventMetricType<ClickExtras>(CommonMetricData(
category: "",
name: "click",
sendInPings: ["store1"],
lifetime: .ping,
disabled: false
), ["object_id"])
XCTAssertNil(metric.testGetValue())
metric.record(ClickExtras(objectId: "buttonA"))
/* SKIPPED: resetting system clock to return fixed time value */
metric.record(ClickExtras(objectId: "buttonB"))
let events = metric.testGetValue()!
XCTAssertEqual(2, events.count)
XCTAssertEqual("click", events[0].name)
XCTAssertEqual("click", events[1].name)
XCTAssertLessThanOrEqual(events[0].timestamp, events[1].timestamp, "The sequence of events must be preserved")
}
func testEventNotRecordedWhenDisabled() {
let metric = EventMetricType<NoExtras>(CommonMetricData(
category: "ui",
name: "click",
sendInPings: ["store1"],
lifetime: .ping,
disabled: true
), nil)
// Attempt to store the event.
metric.record()
// Check that nothing was recorded.
XCTAssertNil(metric.testGetValue(), "Events must not be recorded if they are disabled")
}
func testEventGetValueReturnsNilIfNothingIsStored() {
let metric = EventMetricType<NoExtras>(CommonMetricData(
category: "ui",
name: "click",
sendInPings: ["store1"],
lifetime: .ping,
disabled: false
), nil)
XCTAssertNil(metric.testGetValue())
}
func testEventSavesToSecondaryPings() {
let metric = EventMetricType<ClickExtras>(CommonMetricData(
category: "ui",
name: "click",
sendInPings: ["store1", "store2"],
lifetime: .ping,
disabled: false
), ["object_id"])
XCTAssertNil(metric.testGetValue())
metric.record(ClickExtras(objectId: "buttonA"))
/* SKIPPED: resetting system clock to return fixed time value */
metric.record(ClickExtras(objectId: "buttonB"))
let events = metric.testGetValue("store2")!
XCTAssertEqual(2, events.count)
XCTAssertEqual("ui", events[0].category)
XCTAssertEqual("click", events[0].name)
XCTAssertEqual("ui", events[1].category)
XCTAssertEqual("click", events[1].name)
XCTAssertLessThanOrEqual(events[0].timestamp, events[1].timestamp, "The sequence of events must be preserved")
}
func testEventNotRecordWhenUploadDisabled() {
let metric = EventMetricType<TestExtras>(CommonMetricData(
category: "ui",
name: "click",
sendInPings: ["store1", "store2"],
lifetime: .ping,
disabled: false
), ["test_name"])
Glean.shared.setUploadEnabled(true)
metric.record(TestExtras(testName: "event1"))
let snapshot1 = metric.testGetValue()!
XCTAssertEqual(1, snapshot1.count)
Glean.shared.setUploadEnabled(false)
metric.record(TestExtras(testName: "event2"))
XCTAssertNil(metric.testGetValue())
Glean.shared.setUploadEnabled(true)
metric.record(TestExtras(testName: "event3"))
let snapshot3 = metric.testGetValue()!
XCTAssertEqual(1, snapshot3.count)
}
func testFlushQueuedEventsOnStartup() {
setupHttpResponseStub()
expectation = expectation(description: "Completed upload")
let event = EventMetricType<SomeExtras>(CommonMetricData(
category: "telemetry",
name: "test_event",
sendInPings: ["events"],
lifetime: .ping,
disabled: false
), ["some_extra"])
event.record(SomeExtras(someExtra: "bar"))
Glean.shared.resetGlean(clearStores: false)
waitForExpectations(timeout: 5.0) { error in
XCTAssertNil(error, "Test timed out waiting for upload: \(error!)")
}
let events = lastPingJson?["events"] as? [Any]
XCTAssertNotNil(events)
XCTAssertEqual(1, events?.count)
}
private func getExtraValue(from event: Any?, for key: String) -> String {
let event = event! as! [String: Any]
let extras = event["extra"] as! [String: Any]
return extras[key] as! String
}
func testFlushQueuedEventsOnStartupDroppingPreinitEvents() {
setupHttpResponseStub()
expectation = expectation(description: "Completed upload")
let event = EventMetricType<SomeExtras>(CommonMetricData(
category: "telemetry",
name: "test_event",
sendInPings: ["events"],
lifetime: .ping,
disabled: false
), ["some_extra"])
event.record(SomeExtras(someExtra: "run1"))
XCTAssertEqual(1, event.testGetValue()!.count)
Glean.shared.testDestroyGleanHandle(false)
event.record(SomeExtras(someExtra: "pre-init"))
Glean.shared.resetGlean(clearStores: false)
event.record(SomeExtras(someExtra: "post-init"))
waitForExpectations(timeout: 5.0) { error in
XCTAssertNil(error, "Test timed out waiting for upload: \(error!)")
}
let events = lastPingJson?["events"] as? [Any]
XCTAssertNotNil(events)
XCTAssertEqual(1, events?.count)
XCTAssertEqual("run1", getExtraValue(from: events![0], for: "some_extra"))
setupHttpResponseStub()
expectation = expectation(description: "Completed upload")
Glean.shared.submitPingByName("events")
waitForExpectations(timeout: 5.0) { error in
XCTAssertNil(error, "Test timed out waiting for upload: \(error!)")
}
let events2 = lastPingJson?["events"] as? [Any]
XCTAssertNotNil(events2)
XCTAssertEqual(2, events2?.count)
XCTAssertEqual("pre-init", getExtraValue(from: events2![0], for: "some_extra"))
XCTAssertEqual("post-init", getExtraValue(from: events2![1], for: "some_extra"))
}
func testEventLongExtraRecordsError() {
let metric = EventMetricType<TestExtras>(CommonMetricData(
category: "ui",
name: "click",
sendInPings: ["store1", "store2"],
lifetime: .ping,
disabled: false
), ["test_name"])
metric.record(TestExtras(testName: String(repeating: "0123456789", count: 51)))
XCTAssertEqual(1, metric.testGetNumRecordedErrors(.invalidOverflow))
}
func testEventListener() {
let event1 = EventMetricType<SomeExtras>(CommonMetricData(
category: "telemetry",
name: "test_event1",
sendInPings: ["events"],
lifetime: .ping,
disabled: false
), ["some_extra"])
let event2 = EventMetricType<SomeExtras>(CommonMetricData(
category: "telemetry",
name: "test_event2",
sendInPings: ["events"],
lifetime: .ping,
disabled: false
), ["some_extra"])
let event3 = EventMetricType<SomeExtras>(CommonMetricData(
category: "telemetry",
name: "test_event3",
sendInPings: ["events"],
lifetime: .ping,
disabled: false
), ["some_extra"])
Glean.shared.resetGlean(clearStores: false)
let listener = TestEventListener()
// Register the listener
Glean.shared.registerEventListener(tag: listener.listenerTag, listener: listener)
// Ensure events are being reported via the callback
event1.record(SomeExtras(someExtra: "uno"))
XCTAssertEqual(1, listener.count)
XCTAssertEqual("telemetry.test_event1", listener.lastSeenId)
event2.record(SomeExtras(someExtra: "dos"))
XCTAssertEqual(2, listener.count)
XCTAssertEqual("telemetry.test_event2", listener.lastSeenId)
event3.record(SomeExtras(someExtra: "tres"))
XCTAssertEqual(3, listener.count)
XCTAssertEqual("telemetry.test_event3", listener.lastSeenId)
// Unregister the listener
Glean.shared.unregisterEventListener(tag: listener.listenerTag)
// Ensure events are no longer reported via the callback
event1.record(SomeExtras(someExtra: "uno"))
XCTAssertEqual(3, listener.count)
XCTAssertEqual("telemetry.test_event3", listener.lastSeenId)
event2.record(SomeExtras(someExtra: "dos"))
XCTAssertEqual(3, listener.count)
XCTAssertEqual("telemetry.test_event3", listener.lastSeenId)
event3.record(SomeExtras(someExtra: "tres"))
XCTAssertEqual(3, listener.count)
XCTAssertEqual("telemetry.test_event3", listener.lastSeenId)
}
}