summaryrefslogtreecommitdiffstats
path: root/third_party/rust/glean-core/tests/event.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/glean-core/tests/event.rs')
-rw-r--r--third_party/rust/glean-core/tests/event.rs514
1 files changed, 514 insertions, 0 deletions
diff --git a/third_party/rust/glean-core/tests/event.rs b/third_party/rust/glean-core/tests/event.rs
new file mode 100644
index 0000000000..ed8f7d807f
--- /dev/null
+++ b/third_party/rust/glean-core/tests/event.rs
@@ -0,0 +1,514 @@
+// 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 https://mozilla.org/MPL/2.0/.
+
+mod common;
+use crate::common::*;
+
+use std::collections::HashMap;
+use std::fs;
+
+use glean_core::metrics::*;
+use glean_core::{
+ get_timestamp_ms, test_get_num_recorded_errors, CommonMetricData, ErrorType, Lifetime,
+};
+
+#[test]
+fn record_properly_records_without_optional_arguments() {
+ let store_names = vec!["store1".into(), "store2".into()];
+
+ let (glean, _t) = new_glean(None);
+
+ let metric = EventMetric::new(
+ CommonMetricData {
+ name: "test_event_no_optional".into(),
+ category: "telemetry".into(),
+ send_in_pings: store_names.clone(),
+ disabled: false,
+ lifetime: Lifetime::Ping,
+ ..Default::default()
+ },
+ vec![],
+ );
+
+ metric.record_sync(&glean, 1000, HashMap::new(), 0);
+
+ for store_name in store_names {
+ let events = metric.get_value(&glean, &*store_name).unwrap();
+ assert_eq!(1, events.len());
+ assert_eq!("telemetry", events[0].category);
+ assert_eq!("test_event_no_optional", events[0].name);
+ assert!(events[0].extra.is_none());
+ }
+}
+
+#[test]
+fn record_properly_records_with_optional_arguments() {
+ let (glean, _t) = new_glean(None);
+
+ let store_names = vec!["store1".into(), "store2".into()];
+
+ let metric = EventMetric::new(
+ CommonMetricData {
+ name: "test_event_no_optional".into(),
+ category: "telemetry".into(),
+ send_in_pings: store_names.clone(),
+ disabled: false,
+ lifetime: Lifetime::Ping,
+ ..Default::default()
+ },
+ vec!["key1".into(), "key2".into()],
+ );
+
+ let extra = [
+ ("key1".into(), "value1".into()),
+ ("key2".into(), "value2".into()),
+ ]
+ .iter()
+ .cloned()
+ .collect();
+
+ metric.record_sync(&glean, 1000, extra, 0);
+
+ for store_name in store_names {
+ let events = metric.get_value(&glean, &*store_name).unwrap();
+ let event = events[0].clone();
+ assert_eq!(1, events.len());
+ assert_eq!("telemetry", event.category);
+ assert_eq!("test_event_no_optional", event.name);
+ let extra = event.extra.unwrap();
+ assert_eq!(2, extra.len());
+ assert_eq!("value1", extra["key1"]);
+ assert_eq!("value2", extra["key2"]);
+ }
+}
+
+// SKIPPED record() computes the correct time between events
+// Timing is now handled in the language-specific part.
+
+#[test]
+fn snapshot_returns_none_if_nothing_is_recorded_in_the_store() {
+ let (glean, _t) = new_glean(None);
+
+ assert!(glean
+ .event_storage()
+ .snapshot_as_json(&glean, "store1", false)
+ .is_none())
+}
+
+#[test]
+fn snapshot_correctly_clears_the_stores() {
+ let (glean, _t) = new_glean(None);
+
+ let store_names = vec!["store1".into(), "store2".into()];
+
+ let metric = EventMetric::new(
+ CommonMetricData {
+ name: "test_event_clear".into(),
+ category: "telemetry".into(),
+ send_in_pings: store_names,
+ disabled: false,
+ lifetime: Lifetime::Ping,
+ ..Default::default()
+ },
+ vec![],
+ );
+
+ metric.record_sync(&glean, 1000, HashMap::new(), 0);
+
+ let snapshot = glean
+ .event_storage()
+ .snapshot_as_json(&glean, "store1", true);
+ assert!(snapshot.is_some());
+
+ assert!(glean
+ .event_storage()
+ .snapshot_as_json(&glean, "store1", false)
+ .is_none());
+
+ let files: Vec<fs::DirEntry> = fs::read_dir(&glean.event_storage().path)
+ .unwrap()
+ .filter_map(|x| x.ok())
+ .collect();
+ assert_eq!(1, files.len());
+ assert_eq!("store2", files[0].file_name());
+
+ let snapshot2 = glean
+ .event_storage()
+ .snapshot_as_json(&glean, "store2", false);
+ for s in [snapshot, snapshot2] {
+ assert!(s.is_some());
+ let s = s.unwrap();
+ assert_eq!(1, s.as_array().unwrap().len());
+ assert_eq!("telemetry", s[0]["category"]);
+ assert_eq!("test_event_clear", s[0]["name"]);
+ println!("{:?}", s[0].get("extra"));
+ assert!(s[0].get("extra").is_none());
+ }
+}
+
+// SKIPPED: Events are serialized in the correct JSON format (no extra)
+// SKIPPED: Events are serialized in the correct JSON format (with extra)
+// This test won't work as-is since Rust doesn't maintain the insertion order in
+// a JSON object, therefore you can't check the JSON output directly against a
+// string. This check is redundant with other tests, anyway, and checking against
+// the schema is much more useful.
+
+#[test]
+fn test_sending_of_event_ping_when_it_fills_up() {
+ let (mut glean, _t) = new_glean(None);
+
+ let store_names: Vec<String> = vec!["events".into()];
+
+ for store_name in &store_names {
+ glean.register_ping_type(&PingType::new(
+ store_name.clone(),
+ true,
+ false,
+ true,
+ vec!["max_capacity".to_string()],
+ ));
+ }
+
+ let click = EventMetric::new(
+ CommonMetricData {
+ name: "click".into(),
+ category: "ui".into(),
+ send_in_pings: store_names,
+ disabled: false,
+ lifetime: Lifetime::Ping,
+ ..Default::default()
+ },
+ vec!["test_event_number".into()],
+ );
+
+ // We send 510 events. We expect to get the first 500 in the ping and 10
+ // remaining afterward
+ for i in 0..510 {
+ let mut extra = HashMap::new();
+ extra.insert("test_event_number".to_string(), i.to_string());
+ click.record_sync(&glean, i, extra, 0);
+ }
+
+ assert_eq!(10, click.get_value(&glean, "events").unwrap().len());
+
+ let (url, json, _) = &get_queued_pings(glean.get_data_path()).unwrap()[0];
+ assert!(url.starts_with(format!("/submit/{}/events/", glean.get_application_id()).as_str()));
+ assert_eq!(500, json["events"].as_array().unwrap().len());
+ assert_eq!(
+ "max_capacity",
+ json["ping_info"].as_object().unwrap()["reason"]
+ .as_str()
+ .unwrap()
+ );
+
+ for i in 0..500 {
+ let event = &json["events"].as_array().unwrap()[i];
+ assert_eq!(i.to_string(), event["extra"]["test_event_number"]);
+ }
+
+ let snapshot = glean
+ .event_storage()
+ .snapshot_as_json(&glean, "events", false)
+ .unwrap();
+ assert_eq!(10, snapshot.as_array().unwrap().len());
+ for i in 0..10 {
+ let event = &snapshot.as_array().unwrap()[i];
+ assert_eq!((i + 500).to_string(), event["extra"]["test_event_number"]);
+ }
+}
+
+#[test]
+fn extra_keys_must_be_recorded_and_truncated_if_needed() {
+ let (glean, _t) = new_glean(None);
+
+ let store_names: Vec<String> = vec!["store1".into()];
+
+ let test_event = EventMetric::new(
+ CommonMetricData {
+ name: "testEvent".into(),
+ category: "ui".into(),
+ send_in_pings: store_names,
+ disabled: false,
+ lifetime: Lifetime::Ping,
+ ..Default::default()
+ },
+ vec!["extra1".into(), "truncatedExtra".into()],
+ );
+
+ let test_value = "LeanGleanByFrank";
+ let test_value_long = test_value.to_string().repeat(32);
+ // max length for extra values.
+ let test_value_cap = 500;
+ assert!(
+ test_value_long.len() > test_value_cap,
+ "test value is not long enough"
+ );
+ let mut extra = HashMap::new();
+ extra.insert("extra1".into(), test_value.to_string());
+ extra.insert("truncatedExtra".into(), test_value_long.clone());
+
+ test_event.record_sync(&glean, 0, extra, 0);
+
+ let snapshot = glean
+ .event_storage()
+ .snapshot_as_json(&glean, "store1", false)
+ .unwrap();
+ assert_eq!(1, snapshot.as_array().unwrap().len());
+ let event = &snapshot.as_array().unwrap()[0];
+ assert_eq!("ui", event["category"]);
+ assert_eq!("testEvent", event["name"]);
+ assert_eq!(2, event["extra"].as_object().unwrap().len());
+ assert_eq!(test_value, event["extra"]["extra1"]);
+ assert_eq!(
+ test_value_long[0..test_value_cap],
+ event["extra"]["truncatedExtra"]
+ );
+}
+
+#[test]
+fn snapshot_sorts_the_timestamps() {
+ let (glean, _t) = new_glean(None);
+
+ let metric = EventMetric::new(
+ CommonMetricData {
+ name: "test_event_clear".into(),
+ category: "telemetry".into(),
+ send_in_pings: vec!["store1".into()],
+ disabled: false,
+ lifetime: Lifetime::Ping,
+ ..Default::default()
+ },
+ vec![],
+ );
+
+ metric.record_sync(&glean, 1000, HashMap::new(), 0);
+ metric.record_sync(&glean, 100, HashMap::new(), 0);
+ metric.record_sync(&glean, 10000, HashMap::new(), 0);
+
+ let snapshot = glean
+ .event_storage()
+ .snapshot_as_json(&glean, "store1", true)
+ .unwrap();
+
+ assert_eq!(
+ 0,
+ snapshot.as_array().unwrap()[0]["timestamp"]
+ .as_i64()
+ .unwrap()
+ );
+ assert_eq!(
+ 900,
+ snapshot.as_array().unwrap()[1]["timestamp"]
+ .as_i64()
+ .unwrap()
+ );
+ assert_eq!(
+ 9900,
+ snapshot.as_array().unwrap()[2]["timestamp"]
+ .as_i64()
+ .unwrap()
+ );
+}
+
+#[test]
+fn ensure_custom_ping_events_dont_overflow() {
+ let (glean, _dir) = new_glean(None);
+
+ let store_name = "store-name";
+ let event_meta = CommonMetricData {
+ name: "name".into(),
+ category: "category".into(),
+ send_in_pings: vec![store_name.into()],
+ lifetime: Lifetime::Ping,
+ ..Default::default()
+ };
+ let event = EventMetric::new(event_meta.clone(), vec![]);
+
+ assert!(test_get_num_recorded_errors(
+ &glean,
+ &(event_meta.clone()).into(),
+ ErrorType::InvalidOverflow
+ )
+ .is_err());
+
+ for _ in 0..500 {
+ event.record_sync(&glean, 0, HashMap::new(), 0);
+ }
+ assert!(test_get_num_recorded_errors(
+ &glean,
+ &(event_meta.clone()).into(),
+ ErrorType::InvalidOverflow
+ )
+ .is_err());
+
+ // That should top us right up to the limit. Now for going over.
+ event.record_sync(&glean, 0, HashMap::new(), 0);
+ assert!(
+ test_get_num_recorded_errors(&glean, &event_meta.into(), ErrorType::InvalidOverflow)
+ .is_err()
+ );
+ assert_eq!(501, event.get_value(&glean, store_name).unwrap().len());
+}
+
+/// Ensure that events from multiple runs serialize properly.
+///
+/// Records an event once each in two separate sessions,
+/// ensuring they both show (and an inserted `glean.restarted` event)
+/// in the serialized result.
+#[test]
+fn ensure_custom_ping_events_from_multiple_runs_work() {
+ let (mut tempdir, _) = tempdir();
+
+ let store_name = "store-name";
+ let event = EventMetric::new(
+ CommonMetricData {
+ name: "name".into(),
+ category: "category".into(),
+ send_in_pings: vec![store_name.into()],
+ lifetime: Lifetime::Ping,
+ ..Default::default()
+ },
+ vec![],
+ );
+
+ {
+ let (glean, dir) = new_glean(Some(tempdir));
+ // We don't need a full init. Just to deal with on-disk events:
+ assert!(!glean
+ .event_storage()
+ .flush_pending_events_on_startup(&glean, false));
+ tempdir = dir;
+
+ event.record_sync(&glean, 10, HashMap::new(), 0);
+ }
+
+ {
+ let (glean, _dir) = new_glean(Some(tempdir));
+ // We don't need a full init. Just to deal with on-disk events:
+ assert!(!glean
+ .event_storage()
+ .flush_pending_events_on_startup(&glean, false));
+
+ // Gotta use get_timestamp_ms or this event won't happen "after" the injected
+ // glean.restarted event from `flush_pending_events_on_startup`.
+ event.record_sync(&glean, get_timestamp_ms(), HashMap::new(), 0);
+
+ let json = glean
+ .event_storage()
+ .snapshot_as_json(&glean, store_name, false)
+ .unwrap();
+ assert_eq!(json.as_array().unwrap().len(), 3);
+ assert_eq!(json[0]["category"], "category");
+ assert_eq!(json[0]["name"], "name");
+ assert_eq!(json[1]["category"], "glean");
+ assert_eq!(json[1]["name"], "restarted");
+ assert_eq!(json[2]["category"], "category");
+ assert_eq!(json[2]["name"], "name");
+ }
+}
+
+/// Ensure events in an unregistered, non-"events" (ie Custom) ping are trimmed on a subsequent init
+/// when we pass `true ` for `trim_data_to_registered_pings` in `on_ready_to_submit_pings`.
+#[test]
+fn event_storage_trimming() {
+ let (mut tempdir, _) = tempdir();
+
+ let store_name = "store-name";
+ let store_name_2 = "store-name-2";
+ let event = EventMetric::new(
+ CommonMetricData {
+ name: "name".into(),
+ category: "category".into(),
+ send_in_pings: vec![store_name.into(), store_name_2.into()],
+ lifetime: Lifetime::Ping,
+ ..Default::default()
+ },
+ vec![],
+ );
+ // First, record the event in the two pings.
+ // Successfully records just fine because nothing's checking on record that these pings
+ // exist and are registered.
+ {
+ let (glean, dir) = new_glean(Some(tempdir));
+ tempdir = dir;
+ event.record_sync(&glean, 10, HashMap::new(), 0);
+
+ assert_eq!(1, event.get_value(&glean, store_name).unwrap().len());
+ assert_eq!(1, event.get_value(&glean, store_name_2).unwrap().len());
+ }
+ // Second, construct (but don't init) Glean over again.
+ // Register exactly one of the two pings.
+ // Then process the part of init that does the trimming (`on_ready_to_submit_pings`).
+ // This ought to load the data from the registered ping and trim the data from the unregistered one.
+ {
+ let (mut glean, _dir) = new_glean(Some(tempdir));
+ // In Rust, pings are registered via construction.
+ // But that's done asynchronously, so we do it synchronously here:
+ glean.register_ping_type(&PingType::new(
+ store_name.to_string(),
+ true,
+ false,
+ true,
+ vec![],
+ ));
+
+ glean.on_ready_to_submit_pings(true);
+
+ assert_eq!(1, event.get_value(&glean, store_name).unwrap().len());
+ assert!(event.get_value(&glean, store_name_2).is_none());
+ }
+}
+
+#[test]
+fn with_event_timestamps() {
+ use glean_core::{Glean, InternalConfiguration};
+
+ let dir = tempfile::tempdir().unwrap();
+ let cfg = InternalConfiguration {
+ data_path: dir.path().display().to_string(),
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ language_binding_name: "Rust".into(),
+ upload_enabled: true,
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ app_build: "Unknown".into(),
+ use_core_mps: false,
+ trim_data_to_registered_pings: false,
+ log_level: None,
+ rate_limit: None,
+ enable_event_timestamps: true,
+ experimentation_id: None, // Enabling event timestamps
+ };
+ let glean = Glean::new(cfg).unwrap();
+
+ let store_name = "store-name";
+ let event = EventMetric::new(
+ CommonMetricData {
+ name: "name".into(),
+ category: "category".into(),
+ send_in_pings: vec![store_name.into()],
+ lifetime: Lifetime::Ping,
+ ..Default::default()
+ },
+ vec![],
+ );
+
+ event.record_sync(&glean, get_timestamp_ms(), HashMap::new(), 12345);
+
+ let json = glean
+ .event_storage()
+ .snapshot_as_json(&glean, store_name, false)
+ .unwrap();
+ assert_eq!(json.as_array().unwrap().len(), 1);
+ assert_eq!(json[0]["category"], "category");
+ assert_eq!(json[0]["name"], "name");
+
+ let glean_timestamp = json[0]["extra"]["glean_timestamp"]
+ .as_str()
+ .expect("glean_timestamp should exist");
+ let glean_timestamp: i64 = glean_timestamp
+ .parse()
+ .expect("glean_timestamp should be an integer");
+ assert_eq!(12345, glean_timestamp);
+}